/* 
 * File:   codegenerator.cpp
 * Author: Kamil Chaber
 */

#include "codegenerator.h"

CodeGenerator::CodeGenerator(SymbolTable& symbolTable): symbolTable(symbolTable) {

}

void CodeGenerator::generateJump(string toLabelName) {
    this->generatedASM << "\tjump.i #" << toLabelName << endl << endl;
}

void CodeGenerator::generateLabel(string labelName) {
    this->generatedASM << labelName << ":" << endl;
}

void CodeGenerator::generateAssignment(int lIndex, int rIndex) {
    SymbolTableRecord& leftRec = this->symbolTable.getElement(lIndex);
    SymbolTableRecord& rightRec = this->symbolTable.getElement(rIndex);
    
    //create type conversion if needed
    int conversionTempVariableIndex = this->prepareTypeConversionIfNeeded(leftRec, rightRec);
    
    this->generatedASM << "\tmov." << this->getOperationType(leftRec) << "\t";
    //if the right param was converted than use this converted value
    if (conversionTempVariableIndex != EMPTY_INT_VALUE) {
        SymbolTableRecord& rightParamConverted = this->symbolTable.getElement(conversionTempVariableIndex);
        this->generatedASM << this->getElementRepresentation(rightParamConverted);
    } else {
        this->generatedASM << this->getElementRepresentation(rightRec);
    }
    this->generatedASM << ", ";
    this->generatedASM << this->getElementRepresentation(leftRec);
    this->generatedASM << endl;
}

int CodeGenerator::generateArithmeticOperation(int lIndex, int rIndex, ArithmeticOperationType opType) {
    SymbolTableRecord& leftRec = this->symbolTable.getElement(lIndex);
    SymbolTableRecord& rightRecParam = this->symbolTable.getElement(rIndex);
    
    //create type conversion if needed
    int conversionTempVariableIndex = this->prepareTypeConversionIfNeeded(leftRec, rightRecParam);
    
    switch (opType) {
        case ARITHMETIC_OP_ADDITION: {
            this->generatedASM << "\tadd.";
            break;
        }
        case ARITHMETIC_OP_SUBTRACTION: {
            this->generatedASM << "\tsub.";
            break;
        }
        case ARITHMETIC_OP_MULTIPLICATION: {
            this->generatedASM << "\tmul.";
            break;
        }
        case ARITHMETIC_OP_DIVISION: {
            this->generatedASM << "\tdiv.";
            break;
        }
    }
    
    string operationType = this->getOperationType(leftRec); 
    int resultTempVariableIndex = this->createTemporaryVariable(operationType);
    SymbolTableRecord& tempVariable = this->symbolTable.getElement(resultTempVariableIndex);
    
    this->generatedASM << operationType << " ";
    this->generatedASM << this->getElementRepresentation(leftRec);
    this->generatedASM << ", ";
    //if the right param was converted than use this converted value
    if (conversionTempVariableIndex != EMPTY_INT_VALUE) {
        SymbolTableRecord& rightParamConverted = this->symbolTable.getElement(conversionTempVariableIndex);
        this->generatedASM << this->getElementRepresentation(rightParamConverted);
    } else {
        this->generatedASM << this->getElementRepresentation(rightRecParam);
    } 
    this->generatedASM << ", ";
    this->generatedASM << this->getElementRepresentation(tempVariable);
    this->generatedASM << endl;
    
    return resultTempVariableIndex;
}

int CodeGenerator::generateTermNegation(int termIndex) {
    SymbolTableRecord& rec = this->symbolTable.getElement(termIndex);
    
    string operationType = this->getOperationType(rec);
    int zeroNumberIndex = this->symbolTable.insertNumber((operationType == "r" ? "0.0" : "0"));
    this->symbolTable.setUpElementType(zeroNumberIndex, (operationType == "r" ? RECORD_TYPE_REAL_NUMBER : RECORD_TYPE_INT_NUMBER));
    SymbolTableRecord& zeroNumber = this->symbolTable.getElement(zeroNumberIndex);
    
    int tempVariableIndex = this->createTemporaryVariable(operationType);
    SymbolTableRecord& tempVariable = this->symbolTable.getElement(tempVariableIndex);
    
    this->generatedASM << "\tsub." << operationType << " ";
    this->generatedASM << this->getElementRepresentation(zeroNumber);
    this->generatedASM << ", ";
    this->generatedASM << this->getElementRepresentation(rec);
    this->generatedASM << ", ";
    this->generatedASM << this->getElementRepresentation(tempVariable);
    this->generatedASM << endl;
    
    return tempVariableIndex;
}

void CodeGenerator::generateWriteProc(int expressionIndex) {
    SymbolTableRecord& rec = this->symbolTable.getElement(expressionIndex);
    this->generatedASM <<"\twrite." << this->getOperationType(rec);
    this->generatedASM << " ";
    this->generatedASM << this->getElementRepresentation(rec);
    this->generatedASM << endl;
}

void CodeGenerator::generateExit() {
    this->generatedASM <<"\texit" << endl;
}

string CodeGenerator::getElementRepresentation(SymbolTableRecord& rec) {
    switch(rec.type) {
        case RECORD_TYPE_INT_NUMBER:
        case RECORD_TYPE_REAL_NUMBER: {
            return "#" + rec.lexem;
        }
        case RECORD_TYPE_TEMP_VARIABLE:
        case RECORD_TYPE_VARIABLE: {
            return rec.address;
        }
        default: {
            return "";
        }
    }
}

string CodeGenerator::getOperationType(SymbolTableRecord& leftRec) {
    if (leftRec.isIntType()) {
        return "i";
    } else if (leftRec.isRealType()) {
        return "r";
    }
    
    return "i";
}

int CodeGenerator::prepareTypeConversionIfNeeded(SymbolTableRecord& leftRec, SymbolTableRecord& rightRec) {
    if (leftRec.isIntType() && !rightRec.isIntType() || leftRec.isRealType() && !rightRec.isRealType()) {
        string operationType = this->getOperationType(rightRec);
        int tempVariableIndex = this->createTemporaryVariable(operationType);
        SymbolTableRecord& tempVariable = this->symbolTable.getElement(tempVariableIndex);
        
        //conert the left value
        //conversion to int
        string conversionFunction = "realtoint.";
        //if left factor is real than convert to real
        if (leftRec.isRealType()) {
            conversionFunction = "inttoreal.";
        }
        
        this->generatedASM << "\t" << conversionFunction << operationType << " ";
        this->generatedASM << this->getElementRepresentation(rightRec);
        this->generatedASM << ", ";
        this->generatedASM << this->getElementRepresentation(tempVariable);
        this->generatedASM << endl;
        
        return tempVariableIndex;
    }
    
    return EMPTY_INT_VALUE;
}

int CodeGenerator::createTemporaryVariable(string operationType) {
    int tmpVarOrderNum = 0;
    int lastTempVarIndex = this->symbolTable.getLastTempVariableIndex();
    if (lastTempVarIndex != EMPTY_INT_VALUE) {
        SymbolTableRecord& lastTempVariable = this->symbolTable.getElement(lastTempVarIndex);
        tmpVarOrderNum = lastTempVariable.getOrderNumOfTempVariable() + 1;
    }
    
    stringstream tempVarNameBuilder;
    tempVarNameBuilder << TEMP_VARIABLE_PREFIX << tmpVarOrderNum; 
    string tempVarName = tempVarNameBuilder.str();
    
    SymbolTableRecord tempVariable(tempVarName, RECORD_TYPE_TEMP_VARIABLE);
    if (operationType == "i") {
        tempVariable.varValueType = VAR_VALUE_TYPE_INTEGER;
    } else if (operationType == "r") {
        tempVariable.varValueType = VAR_VALUE_TYPE_REAL;
    }
 
    int tempVarIndex = this->symbolTable.insertRecord(tempVariable);
    this->symbolTable.allocateAddress(tempVarIndex);
    
    return tempVarIndex;
}

void CodeGenerator::createASMFile(const char* outputFileName) {
    ofstream outputASMStream;
    outputASMStream.open(outputFileName, fstream::out);
    outputASMStream << this->generatedASM.str();
}

string CodeGenerator::toString() {
    return this->generatedASM.str();
}

CodeGenerator::~CodeGenerator() {
}

